﻿using System;
using System.IO;
using System.Threading;
using System.Reflection;
using System.ServiceModel;
using System.Collections.Generic;
using System.ServiceModel.Discovery;

using Builderator.Core.Action;
using Builderator.Core.Plugin;
using Builderator.Core.Database;
using Builderator.Core.Components;
using Builderator.Core.Interfaces;

namespace Builderator.Core
{
    public enum ActionUpdatedEnum { Add, Remove, Update };

    public delegate void ActionUpdatedDelegate(IAction action, ActionUpdatedEnum type);

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, IncludeExceptionDetailInFaults = true)]
    public class BuilderatorEngine : IBuilderatorEngine, IDisposable
    {
        private bool disposed = false;
        private BuilderatorSettings settings;
        private DatabaseAdapter databaseAdapter;

        public event ActionUpdatedDelegate ActionUpdatedEvent;

        private static readonly List<IBuilderatorEngineCallback> subscribers = new List<IBuilderatorEngineCallback>();
        
        public List<DatabaseInfo> Servers
        {
            get
            {
                return this.databaseAdapter.GetServers();
            }
        }

        public JobList Jobs
        {
            get
            {
                return this.GetJobHistory();
            }
        }

        public string AttachedHostname
        {
            get
            {
                return this.settings.Name;
            }
        }

        public BuilderatorEngine()
        {
            try
            {
                this.databaseAdapter = new DatabaseAdapter();

                this.settings = (BuilderatorSettings)this.GetSettings();
#if DEBUG
                this.databaseAdapter.RegisterPlugin(new MSBuildPlugin());
                this.databaseAdapter.RegisterPlugin(new NAntPlugin());
                this.databaseAdapter.RegisterPlugin(new NSISPlugin());
                this.databaseAdapter.RegisterPlugin(new CopyPlugin());
                this.databaseAdapter.RegisterPlugin(new GoogleCodePlugin());
                this.databaseAdapter.RegisterPlugin(new SubversionPlugin());
                this.databaseAdapter.RegisterPlugin(new IntegrationTrigger());
                this.databaseAdapter.RegisterPlugin(new IntervalTrigger());
#else
                this.LoadPlugins(this.settings.Plugins);
#endif
            }
            catch (Exception e)
            {
                this.DebugMessage(e.Message);

                this.DebugMessage(e.StackTrace);
            }
        }

        #region Project

        public Guid CreateProject(string name)
        {
            ProjectAction projectAction = new ProjectAction(name);

            projectAction.Variables.Add(new Variable("ProjectPath", VariableType.Path, "C:\\" + name + "\\", false));

            this.databaseAdapter.Insert(DatabaseTable.Actions, (IDatabase)projectAction);

            return projectAction.Identifier;
        }

        public List<IAction> GetProjects()
        {
            IAction action;
            List<IAction> projectList = new List<IAction>();
            ProjectAction projectAction = new ProjectAction();

            foreach (string[] projectSetting in this.databaseAdapter.Select(string.Format("SELECT settings FROM actions WHERE type='{0}'", projectAction.Type)))
            {
                action = (IAction)ActionBase.Deserialize(projectSetting[0], typeof(ProjectAction));

                projectList.Add(action);
            }

            return projectList;
        }

        public IAction GetProject(Guid identifier)
        {
            List<string[]> projectList;
            ProjectAction projectAction = new ProjectAction();

            projectAction.Identifier = identifier;

            projectList = this.databaseAdapter.Select(DatabaseTable.Actions, (IDatabase)projectAction);

            return (IAction)ActionBase.Deserialize(projectList[0][6], typeof(ProjectAction));
        }

        public void UpdateAction(IDatabase action)
        {
            if (action is ITrigger)
            {
                this.ActionUpdatedEvent((IAction)action, ActionUpdatedEnum.Update);
            }

            this.databaseAdapter.Update(DatabaseTable.Actions, (IDatabase)action);
        }

        public void DeleteProject(Guid identifier)
        {
            ProjectAction action = new ProjectAction();

            action.Identifier = identifier;

            this.databaseAdapter.Delete(DatabaseTable.Actions, (IDatabase)action);

            this.GetAssociatedActions(identifier, action);
        }

        #endregion

        #region Build

        public Guid CreateBuild(string buildName, Guid projectIdentifier)
        {
            ProjectAction projectAction;
            BuildAction buildAction = new BuildAction(buildName, projectIdentifier);

            projectAction = (ProjectAction)this.GetProject(projectIdentifier);

            Variable projectPath = projectAction.Variables.Find("ProjectPath");

            buildAction.Variables.Add(new Variable("BuildPath", VariableType.Path, projectPath.Value + buildAction.Name + "\\", false));

            foreach (Variable variable in projectAction.Variables)
            {
                buildAction.Variables.Add(variable);
            }

            this.databaseAdapter.Insert(DatabaseTable.Actions, (IDatabase)buildAction);

            return buildAction.Identifier;
        }

        public IDocument GetBuild(Guid identifier)
        {
            BuildAction buildAction = new BuildAction();

            buildAction.Identifier = identifier;

            buildAction = (BuildAction)ActionBase.Deserialize(this.databaseAdapter.Select(DatabaseTable.Actions, (IDatabase)buildAction)[0][6], typeof(BuildAction));

            buildAction.Source = (List<IAction>)this.GetAssociatedActionsBySubType(buildAction.Identifier, typeof(ISource));

            buildAction.Variables.Update("Major", buildAction.Version.Major.ToString());

            buildAction.Variables.Update("Minor", buildAction.Version.Minor.ToString());

            buildAction.Variables.Update("Build", buildAction.Version.Build.ToString());

            buildAction.Variables.Update("Revision", buildAction.Version.Revision.ToString());

            return buildAction;
        }

        public List<IAction> GetBuilds(Guid identifier)
        {
            BuildAction action;
            BuildAction buildAction = new BuildAction();
            List<IAction> buildList = new List<IAction>();

            foreach (string[] buildSetting in this.databaseAdapter.Select(string.Format("SELECT settings FROM actions WHERE associated='{0}'", identifier)))
            {
                try
                {
                    action = (BuildAction)ActionBase.Deserialize(buildSetting[0], typeof(BuildAction));
                }
                catch (Exception)
                {
                    continue;
                }

                buildList.Add(action);
            }

            return buildList;
        }

        public void DeleteBuild(Guid identifier)
        {
            BuildAction action = new BuildAction();

            action.Identifier = identifier;

            this.databaseAdapter.Delete(DatabaseTable.Actions, (IDatabase)action);

            this.GetAssociatedActions(identifier, action);
        }

        #endregion

        #region Action

        public Guid CreateAction(Guid buildIdentifier, Guid type)
        {
            IAction plugin;
            BuildAction buildAction;
            List<string[]> pluginType;

            pluginType = this.databaseAdapter.Select(string.Format("SELECT * FROM plugins WHERE identifier='{0}'", type));

            try
            {
                plugin = (IAction)ActionBase.Deserialize(Type.GetType(pluginType[0][1]));
            }
            catch (Exception)
            {
                return Guid.Empty;
            }

            plugin.Identifier = Guid.NewGuid();

            buildAction = (BuildAction)this.GetBuild(buildIdentifier);

            plugin.Associated = buildIdentifier;

            if (plugin is ITrigger)
            {
                this.databaseAdapter.Insert(DatabaseTable.Actions, (IDatabase)plugin);

                return plugin.Identifier;
            }

            IAction lastAction;
            List<string[]> action;

            action = this.databaseAdapter.Select(string.Format("SELECT actions.settings, plugins.type FROM actions INNER JOIN plugins ON actions.type=plugins.identifier WHERE associated='{0}' AND next='{1}'", buildIdentifier, Guid.Empty));

            if (action.Count > 0)
            {
                lastAction = (IAction)ActionBase.Deserialize(action[0][0], Type.GetType(action[0][1]));

                lastAction.Next = plugin.Identifier;

                plugin.Previous = lastAction.Identifier;

                this.databaseAdapter.Update(DatabaseTable.Actions, (IDatabase)lastAction);
            }

            if (plugin is IPlugin)
            {
                IPlugin temp = plugin as IPlugin;

                foreach (Variable variable in buildAction.Variables)
                {
                    temp.Variables.Add(variable);
                }
            }

            this.databaseAdapter.Insert(DatabaseTable.Actions, (IDatabase)plugin);

            return plugin.Identifier;
        }

        public IDocument GetAction(Guid identifier)
        {
            try
            {
                List<string[]> actionTable;

                actionTable = this.databaseAdapter.Select(string.Format("SELECT actions.settings, plugins.type FROM actions INNER JOIN plugins ON actions.type=plugins.identifier WHERE actions.identifier='{0}'", identifier));

                return (IDocument)ActionBase.Deserialize(actionTable[0][0], Type.GetType(actionTable[0][1]));
            }
            catch (Exception)
            {
                return null;
            }
        }

        public IAction GetAction(Guid identifier, Guid type)
        {
            List<string[]> pluginType;

            pluginType = this.databaseAdapter.Select(string.Format("SELECT actions.settings, plugins.type FROM actions INNER JOIN plugins ON actions.type=plugins.identifier WHERE actions.identifier='{0}'", identifier));

            return (IAction)ActionBase.Deserialize(pluginType[0][0], Type.GetType(pluginType[0][1]));
        }

        public IAction GetAction(string sqlCommand)
        {
            try
            {
                List<string[]> actionTable;
                List<string[]> pluginTable;
                ProjectAction action = new ProjectAction();

                actionTable = this.databaseAdapter.Select(sqlCommand);

                action.Type = new Guid(actionTable[0][2]);

                pluginTable = this.databaseAdapter.Select(DatabaseTable.Plugins, (IDatabase)action);

                return (IAction)ActionBase.Deserialize(actionTable[0][7], Type.GetType(pluginTable[0][1]));
            }
            catch (Exception)
            {
                return null;
            }
        }

        public List<IAction> GetActions(Guid buildIdentifier)
        {
            List<string[]> actionSettings = null;
            List<IAction> actionList = new List<IAction>();
            List<IAction> orderList = new List<IAction>();
            List<IAction> organizeList = new List<IAction>();

            actionSettings = this.databaseAdapter.Select(string.Format("SELECT actions.settings, plugins.type FROM actions INNER JOIN plugins ON actions.type=plugins.identifier WHERE actions.associated='{0}'", buildIdentifier.ToString()));

            IAction pluginAction;

            foreach (string[] actionSetting in actionSettings)
            {
                try
                {
                    pluginAction = (IAction)ActionBase.Deserialize(actionSetting[0], Type.GetType(actionSetting[1]));
                }
                catch (Exception)
                {
                    continue;
                }

                actionList.Add(pluginAction);
            }

            IAction lastAction = null;

            foreach (IAction action in actionList)
            {
                if (action is ITrigger)
                    orderList.Add(action);
            }

            while (true)
            {
                foreach (IAction order in actionList)
                {
                    if (order.Previous == Guid.Empty)
                    {
                        if (orderList.Contains(order))
                            continue;

                        lastAction = order;

                        orderList.Add(order);

                        break;
                    }

                    if (lastAction != null && order.Previous == lastAction.Identifier)
                    {
                        lastAction = order;

                        orderList.Add(order);

                        break;
                    }
                }

                if (actionList.Count == orderList.Count)
                    break;
            }

            return orderList;
        }

        private void GetAssociatedActions(Guid identifier, IDatabase action)
        {
            Guid tempIdent;
            List<string[]> associatedActions = null;

            associatedActions = this.databaseAdapter.Select(string.Format("SELECT * FROM actions WHERE associated='{0}'", identifier));

            foreach (string[] associatedAction in associatedActions)
            {
                tempIdent = new Guid(associatedAction[0]);

                ((IAction)action).Identifier = tempIdent;

                this.databaseAdapter.Delete(DatabaseTable.Actions, action);

                this.GetAssociatedActions(tempIdent, action);
            }
        }

        public List<IAction> GetAssociatedActionsBySubType(Guid associated, Type type)
        {
            IAction plugin;
            List<IAction> pluginList = new List<IAction>();
            List<string[]> associatedActions;

            associatedActions = this.databaseAdapter.Select(string.Format("SELECT identifier FROM actions WHERE associated='{0}'", associated));

            foreach (string[] action in associatedActions)
            {
                plugin = (IAction)this.GetAction(new Guid(action[0]));

                Type[] pluginInterfaces = plugin.GetType().FindInterfaces(new TypeFilter(delegate(Type objType, object objCriteria)
                    {
                        if (objType.ToString() == objCriteria.ToString())
                            return true;
                        else
                            return false;
                    }), type);

                if (pluginInterfaces.Length > 0)
                    pluginList.Add(plugin);
            }

            return pluginList;
        }

 

        public void DeleteAction(Guid identifier)
        {
            IAction previousAction, nextAction;
            ProjectAction action = new ProjectAction();

            action.Identifier = identifier;

            previousAction = this.GetAction(string.Format("SELECT * FROM actions WHERE next='{0}'", identifier));

            nextAction = this.GetAction(string.Format("SELECT * FROM actions WHERE previous='{0}'", identifier));

            this.databaseAdapter.Delete(DatabaseTable.Actions, (IDatabase)action);

            if (previousAction == null && nextAction == null)
                return;

            if (previousAction == null)
            {
                nextAction.Previous = Guid.Empty;

                this.databaseAdapter.Update(DatabaseTable.Actions, (IDatabase)nextAction);
            }
            else if (nextAction == null)
            {
                previousAction.Next = Guid.Empty;

                this.databaseAdapter.Update(DatabaseTable.Actions, (IDatabase)previousAction);
            }
            else
            {
                nextAction.Previous = previousAction.Identifier;

                previousAction.Next = nextAction.Identifier;

                this.databaseAdapter.Update(DatabaseTable.Actions, (IDatabase)nextAction);

                this.databaseAdapter.Update(DatabaseTable.Actions, (IDatabase)previousAction);
            }
        }

        #endregion

        #region Execution

        public void RunAction(Guid actionIdentifier)
        {
            Job job;
            IPlugin action = (IPlugin)this.GetAction(actionIdentifier);
            BuildAction buildAction = (BuildAction)this.GetBuild(action.Associated);
            ProjectAction projectAction = (ProjectAction)this.GetProject(buildAction.Associated);

            action.DebugMessageEvent += new DebugMessageDelegate(this.DebugMessage);

            action.Variables = buildAction.Variables;

            if (action is ISource)
            {
                int revision = 0;

                revision = ((ISource)action).GetRevision();

                if (revision > buildAction.Version.Revision)
                {
                    buildAction.Version.Revision = revision;

                    this.databaseAdapter.Update(DatabaseTable.Actions, (IDatabase)buildAction);

                    this.BuildUpdate();
                }
            }

            foreach (Variable variable in projectAction.Variables)
            {
                buildAction.Variables.Add(variable);
            }

            action.Variables = buildAction.Variables;

            job = new Job(action) { Associated = buildAction.Identifier };

            job.JobStatusChanged += new JobStatusChangedDelegate(job_JobStatusChanged);

            this.databaseAdapter.Insert(DatabaseTable.Jobs, (IDatabase)job);

            this.HistoryUpdate();

            new Thread(new ThreadStart(job.ExecuteThread)).Start();
        }

        private void job_JobStatusChanged(object sender, JobStatusEventArgs e)
        {
            Job job = sender as Job;

            job.Status = e.Status;

            this.databaseAdapter.Update(DatabaseTable.Jobs, (IDatabase)job);

            this.HistoryUpdate();
        }

        public void RunBuild(Guid buildIdentifier)
        {
            Job job;
            List<Job> jobList = new List<Job>();
            List<IAction> actionList = this.GetActions(buildIdentifier);
            BuildAction build = (BuildAction)this.GetBuild(buildIdentifier);
            ProjectAction project = (ProjectAction)this.GetProject(build.Associated);

            build.Version.Build++;

            this.databaseAdapter.Update(DatabaseTable.Actions, (IDatabase)build);

            this.BuildUpdate();

            for (int i = 0; i < actionList.Count; i++)
            {
                if (actionList[i] is ITrigger)
                    actionList.Remove(actionList[i]);
            }

            foreach (Variable variable in project.Variables)
            {
                build.Variables.Add(variable);
            }

            foreach (IPlugin action in actionList)
            {
                action.DebugMessageEvent += new DebugMessageDelegate(this.DebugMessage);

                if (action is ISource)
                {
                    int revision = ((ISource)action).GetRevision();

                    build.Version.Revision = revision;

                    this.databaseAdapter.Update(DatabaseTable.Actions, (IDatabase)build);

                    this.BuildUpdate();
                }

                action.Variables = build.Variables;

                job = new Job(action) { Associated = buildIdentifier };

                job.Executed = DateTime.Now;

                job.JobStatusChanged += new JobStatusChangedDelegate(job_JobStatusChanged);

                this.databaseAdapter.Insert(DatabaseTable.Jobs, (IDatabase)job);

                jobList.Add(job);
            }

            Job buildJob = new Job(jobList) { Name = build.Name };

            buildJob.Executed = DateTime.Now;

            buildJob.DebugMessageEvent += new DebugMessageDelegate(this.DebugMessage);

            buildJob.JobStatusChanged += new JobStatusChangedDelegate(job_JobStatusChanged);

            this.databaseAdapter.Insert(DatabaseTable.Jobs, (IDatabase)buildJob);

            this.HistoryUpdate();

            new Thread(new ThreadStart(buildJob.ExecuteThread)).Start();
        }

        #endregion

        private void LoadPlugins(string path)
        {
            if (!Directory.Exists(path))
                return;

            IAction action;

            foreach (string file in Directory.GetFiles(path))
            {
                Assembly loadedAssemby = Assembly.LoadFrom(file);

                foreach (Type type in loadedAssemby.GetTypes())
                {
                    switch (type.BaseType.ToString())
                    {
                        case "Builderator.Core.Trigger.TriggerBase":
                        case "Builderator.Core.Plugin.PluginBase":
                            {
                                action = (IAction)loadedAssemby.CreateInstance(type.FullName);

                                this.databaseAdapter.RegisterPlugin((IDatabase)action);
                            }
                            break;
                        default:
                            continue;
                    }
                }
            }
        }

        public void BuildUpdate()
        {
            subscribers.ForEach(delegate(IBuilderatorEngineCallback callback)
            {
                if (((ICommunicationObject)callback).State == CommunicationState.Opened)
                {
                    callback.OnBuildUpdate();
                }
                else
                {
                    subscribers.Remove(callback);
                }
            });
        }

        public void HistoryUpdate()
        {
            subscribers.ForEach(delegate(IBuilderatorEngineCallback callback)
            {
                if (((ICommunicationObject)callback).State == CommunicationState.Opened)
                {
                    callback.OnHistoryUpdate();
                }
                else
                {
                    subscribers.Remove(callback);
                }
            });
        }

        public void DebugMessage(string message)
        {
            System.Diagnostics.Trace.WriteLine(message);

            System.Diagnostics.Debug.WriteLine(message);

            subscribers.ForEach(delegate(IBuilderatorEngineCallback callback)
            {
                if (((ICommunicationObject)callback).State == CommunicationState.Opened)
                {
                    callback.OnDebugMessage(message);
                }
                else
                {
                    subscribers.Remove(callback);
                }
            });
        }

        public void Subscribe()
        {
            try
            {
                IBuilderatorEngineCallback callback = OperationContext.Current.GetCallbackChannel<IBuilderatorEngineCallback>();

                if (!subscribers.Contains(callback))
                {
                    subscribers.Add(callback);

                    this.DebugMessage("Adding callback");
                }
            }
            catch (Exception e)
            {
                this.DebugMessage(e.Message);

                this.DebugMessage(e.StackTrace);
            }
        }

        public void Unsubscribe()
        {
            try
            {
                IBuilderatorEngineCallback callback = OperationContext.Current.GetCallbackChannel<IBuilderatorEngineCallback>();

                if (subscribers.Contains(callback))
                {
                    subscribers.Remove(callback);

                    this.DebugMessage("Removing callback");
                }
            }
            catch (Exception e)
            {
                this.DebugMessage(e.Message);

                this.DebugMessage(e.StackTrace);
            }
        }

        public JobList GetJobHistory()
        {
            JobList jobs = new JobList();

            foreach (string[] item in this.databaseAdapter.Select("SELECT settings FROM jobs"))
            {
                jobs.Add((Job)Job.Deserialize(item[0], typeof(Job)));
            }

            return jobs;
        }

        public List<IAction> GetTriggers()
        {
            IAction triggerAction;
            List<string[]> triggerSettings = null;
            List<IAction> triggerList = new List<IAction>();

            foreach (IAction action in this.GetPlugins())
            {
                if (action is ITrigger)
                {
                    triggerSettings = this.databaseAdapter.Select(string.Format("SELECT actions.settings, plugins.type FROM actions INNER JOIN plugins ON actions.type=plugins.identifier WHERE actions.type='{0}'", action.Type));

                    foreach (string[] settings in triggerSettings)
                    {
                        triggerAction = (IAction)ActionBase.Deserialize(triggerSettings[0][0], Type.GetType(triggerSettings[0][1]));

                        if (triggerAction is IDependency)
                        {
                            ((IDependency)triggerAction).SourceActions = this.GetAssociatedActionsBySubType(triggerAction.Associated, typeof(ISource));
                        }

                        triggerList.Add(triggerAction);
                    }
                }
            }

            return triggerList;
        }

        public List<IAction> GetPlugins()
        {
            List<string[]> pluginSettings = null;
            List<IAction> pluginList = new List<IAction>();
            ProjectAction pluginAction = new ProjectAction();

            pluginSettings = this.databaseAdapter.Select(string.Format("SELECT * FROM plugins"));

            IAction action;

            foreach (string[] pluginSetting in pluginSettings)
            {
                try
                {
                    action = (IAction)ActionBase.Deserialize(Type.GetType(pluginSetting[1]));
                }
                catch (Exception)
                {
                    continue;
                }

                pluginList.Add(action);
            }

            return pluginList;
        }

        public IDocument GetSettings()
        {
            List<string[]> settings;
            IDocument settingsDocument = null;

            settings = this.databaseAdapter.Select(string.Format("SELECT settings FROM actions WHERE identifier='{0}'", Guid.Empty));

            if (settings.Count == 0)
            {
                BuilderatorSettings builderatorSettings;

                builderatorSettings = new BuilderatorSettings();

                builderatorSettings.Servers.Add(builderatorSettings.Name);

                this.databaseAdapter.Insert(DatabaseTable.Actions, (IDatabase)builderatorSettings);

                settingsDocument = builderatorSettings;
            }
            else
            {
                settingsDocument = (IDocument)ActionBase.Deserialize(settings[0][0], typeof(BuilderatorSettings));
            }

            return settingsDocument;
        }

        public void Reconnect(string serverName)
        {
            this.databaseAdapter.Reconnect(serverName);
        }

        public void DeleteJobHistory(Job job)
        {
            this.databaseAdapter.Delete(DatabaseTable.Jobs, (IDatabase)job);

            this.HistoryUpdate();
        }

        #region IDisposable Implementation

        ~BuilderatorEngine()
        {
            this.Dispose(false);
        }

        public void Dispose()
        {
            this.Dispose(true);

            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    //managed resources
                    this.Unsubscribe();
                }

                //unmanaged resources
            }

            this.disposed = true;
        }

        #endregion
    }
}